import { genIdInSelector, genIdSelector } from './genIdInSelector'

export default class Entity {
    constructor(db, collectionName) {
        this.collection = db.collection(collectionName)
    }

    transformId(entry) {
        if (entry._id) {
            entry.id = entry._id.toString()
            delete entry._id
        }

        return entry
    }

    async find(selector = {}, option = { sort: null, limit: 1000, project: null, addFields: null }) {
        const { sort = null, limit = 1000, project = null, addFields = null } = option

        const result = await this.collection.aggregate(
            [
                { $match: selector },
                sort ? { $sort: sort } : null,
                { $limit: limit },
                addFields ? { $addFields: addFields } : null,
                project ? { $project: project } : null,
            ].filter(Boolean)
        ).toArray()

        return result.map(this.transformId)
    }

    async aggregate(aggregate) {
        const result = await this.collection.aggregate(aggregate).toArray()
        return result.map(this.transformId)
    }

    async findWithExcludeOption(selector = {}, excludeOption = {}) {
        const pipeline = [
            {
                $match: selector
            }, {
                $project: excludeOption
            }
        ]

        const result = await this.collection.aggregate(pipeline).toArray()

        return result.map(this.transformId)
    }

    async findByIds(ids) {
        const result = await this.find(genIdInSelector(ids))
        return result
    }

    appendFieldOnInsert(item = {}) {
        return {
            ...item,
            create_at: +new Date()
        }
    }

    appendFieldOnUpdate(item = {}) {
        return {
            ...item,
            update_at: +new Date()
        }
    }

    async insert(entry) {
        const clone = entry.__skipClone__ ? entry : JSON.parse(JSON.stringify(entry))
        delete clone.id
        delete clone.__skipClone__

        const newEntry = await this.collection.insertOne(this.appendFieldOnInsert(clone))
        return newEntry
    }

    async insertMany(entryList) {
        const clone = JSON.parse(JSON.stringify(entryList)).map(item => {
            delete item.id
            return this.appendFieldOnInsert(item)
        })

        const newEntryList = await this.collection.insertMany(clone)
        return newEntryList
    }

    async delete(selector = { _id: 'avoid remove all' }) {
        await this.collection.remove(selector)
    }

    async deleteById(id) {
        await this.collection.remove(genIdSelector(id))
    }

    async updateById(id, toUpdateRecord = {}) {
        await this.collection.update(genIdSelector(id), { $set: this.appendFieldOnUpdate(toUpdateRecord) })
    }

    genToAddItem(_item) {
        throw 'not implemented'
    }

    genToUpdateItem(_item) {
        throw 'not implemented'
    }

    async saveList(list) {
        if (list.length) {
            const toAddList = list
                .filter(item => item.mode === 'add')
                .map(item => this.genToAddItem(item))
                .map(item => this.appendFieldOnInsert(item))

            toAddList.length && await this.insertMany(toAddList)

            const toDeleteIdList = list
                .filter(item => item.mode === 'delete')
                .map(item => item.id)

            toDeleteIdList.length && await this.delete(genIdInSelector(toDeleteIdList))

            const toUpdateList = list
                .filter(item => item.mode === 'edit')
                .map(item => ({
                    id: item.id,
                    entry: this.genToUpdateItem(item)
                }))
                .map(item => this.appendFieldOnUpdate(item))

            toUpdateList.forEach(async item => {
                await this.updateById(item.id, item.entry)
            })
        }
    }

    async groupBy(condition, groupBy) {
        const pipeline = [
            {
                $match: condition
            }, {
                $group: {
                    _id: groupBy,
                    count: { $sum: 1 }
                }
            }
        ]

        const result = await this.collection.aggregate(pipeline).toArray()
        return result.map(this.transformId)
    }
}